import sun.misc.Unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;



/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ws
 * Date: 2023-12-23
 * Time: 16:35
 */


// ===================java 锁的种类==================


public class T1 {
    public static void main(String[] args) throws InterruptedException {

        // 公平锁和非公平锁

        // 默认非公平锁，加 true 参数为公平锁
//        Lock lock = new ReentrantLock(true);

        /**
         *     public ReentrantLock() {
         *         sync = new NonfairSync();
         *     }
         *
         *     /**
         *      * Creates an instance of {@code ReentrantLock} with the
         *      * given fairness policy.
         *      *
         *      * @param fair {@code true} if this lock should use a fair ordering policy
         *      */
        /**     public ReentrantLock( boolean fair){
         *          sync = fair ? new FairSync() : new NonfairSync();
         *      }
         */

//         synchronized ()
        /**
         * 非公平锁
         * 非公平锁的优点是吞吐量比公平锁大
         */



        // 可重入锁（递归锁）
        // synchronized 和 ReentrantLock 都是
        /**
         * 指的是同一线程外层函数获得锁之后﹐内层递归函数仍然能获取该锁的代码，在同一个线程在外层方法获取锁的时候，在进入内层方法会自动获取锁
         * 也就是说，线程可以进入任何一个它已经拥有的锁所同步着的代码块。
         *
         * 实际上只上了一把锁，如家中防盗门和卫生间的门，睡觉时只锁大门
         *
         * 最大的作用是能够避免死锁问题
         */


//        Phone phone = new Phone();
//
//        new Thread(() -> {
//            phone.sendMessage();
//        }, "t1").start();
//
//        new Thread(() -> {
//            phone.sendMessage();
//        }, "t2").start();
//
//        Thread.sleep(1000);
//
//        System.out.println();
//        System.out.println("#######################");
//        System.out.println();
//
//        new Thread(phone, "t3").start();
//        new Thread(phone, "t4").start();


        // 自旋锁


        /** Unsafe 类 + CAS
         * 是指尝试获取锁的线程不会立即阻塞，而是采用循环的方式去尝试获取锁，这样的好处是减少线程上下文切换的消耗,
         * 缺点是循环会消耗CPU
         *
         *     public final int getAndAddInt(Object var1, long var2, int var4) {
         *         int var5;
         *         do {
         *             var5 = this.getIntVolatile(var1, var2);
         *         } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
         *
         *         return var5;
         *     }
         */


//        SpinLockDemo spinLockDemo = new SpinLockDemo();
//
//        new Thread(() -> {
//            spinLockDemo.myLock();
//            // 暂停一会线程，模拟一直占用，不能释放锁
//            try {
//                System.out.println("阻塞中......");
//                Thread.sleep(5000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            spinLockDemo.myUnLock();
//        }, "AA").start();


//        BB 线程需要等待 AA 线程将锁释放出来，


//        new Thread(() -> {
//            spinLockDemo.myLock();
//            // 暂停一会线程
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            spinLockDemo.myUnLock();
//        }, "BB").start();



        // 独占锁(写锁)/共享锁(读锁)/互斥锁

        /**
         * 独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁
         * 共享锁:指该锁可被多个线程所持有。
         * 对 ReentrantReadWriteLock 其读锁是共亨锁，其写锁是独占锁。既保证了数据的一致性，又保证了高并发性
         * 读锁的共享锁可保证并发读是非常高效的，读写，写读，写写的过程是互斥的。
         * 即写操作是独占的，原子的，不许分割
         */
        MyCache myCache = new MyCache();
        // 写
        for (int i = 1; i <= 5; i++) {
            final int tmpInt = i;
            new Thread(() -> {
                try {
                    myCache.put(tmpInt + "", tmpInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

        // 读
        for (int i = 1; i <= 5; i++) {
            final int tmpInt = i;
            new Thread(() -> {
                try {
                    myCache.get(tmpInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

        /**
         * 没有加锁，写操作多次被打断
         * 1	“正在写入：1”
         * 5	“正在写入：5“
         * 4	”正在写入：4”
         * 3	正在写入：3
         * 2	正在写入：2
         * 1	正在读取
         * 2	正在读取
         *
         * 加了 ReadWriteLock，写入操作连续
         * 2	"正在写入：2"
         * 2	"写入完成"
         * 1	正在写入：1
         * 1	写入完成
         * 3	正在写入：3
         * 3	写入完成
         */

    }
}


class MyCache {// 资源类（缓存操作，需要较高地并发操作）
    private volatile Map<String, Object> map = new HashMap<>();
    // 这个锁不足以满足，多个线程的同时读写
//    private Lock lock = new ReentrantLock();
    // 读写锁分离
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//



    /**
     * 写操作，不可共享
     * @param key
     * @param val
     * @throws InterruptedException
     */
    public void put(String key, Object val) throws InterruptedException {
        try {
            // 拿到 writeLock，加锁
            readWriteLock.writeLock().lock();//

            System.out.println(Thread.currentThread().getName() + "\t正在写入：" + key);
            Thread.sleep(300);
            map.put(key, val);
            System.out.println(Thread.currentThread().getName() + "\t写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            readWriteLock.writeLock().unlock();//
        }
    }


    /**
     * 读操作，可以共享
     * @param key
     * @throws InterruptedException
     */
    public void get(String key) throws InterruptedException {
        try {
            // 拿到 readLock 加锁
            readWriteLock.readLock().lock();

            System.out.println(Thread.currentThread().getName() + "\t正在读取");
            Thread.sleep(300);
            System.out.println(Thread.currentThread().getName() + "\t读取完成：" + map.get(key));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            readWriteLock.readLock().unlock();
        }
    }
}





class Phone implements Runnable{

    // 证明 synchronized 是可重入锁

    /**
     * 两个 synchronized 能够嵌套式使用
     *
     *
     */
    public synchronized void sendMessage() {
        // 调用该方法第一次加锁
        System.out.println(Thread.currentThread().getName() + "\tsendMessage");
        // 第二次加锁，使用 sendMessage 的锁，即共用同一把锁
        sendEmail();
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\tsendEmail");
    }



    // 证明 ReentrantLock
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        get();
    }

    public void get() {
        // ReentrantLock 需要手动加锁解锁
        // 但是要一一配对，只要配对，多少都可以
        // 如果这里 lock 两次，unlock 一次就会导致线程一直阻塞等待解锁
        lock.lock();
//        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tget");
            // 方法内部再次加锁
            set();
        } finally {
            // 不配对，会一直死锁
            lock.unlock();
//            lock.unlock();
        }
    }

    public void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tset");
        } finally {
            lock.unlock();
        }
    }
}


class SpinLockDemo {
    // 原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 此时 atomicReference 内的线程为 null
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\tcome in");
        // 将 thread 内的线程赋给 atomicReference，一直自旋直到成功
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void myUnLock() {
        Thread thread = Thread.currentThread();
        // 解锁
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\tcome out");
    }
}